﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NativeWifi;
using System.Net.NetworkInformation;
using System.Net;
using System.ComponentModel;

namespace Client {

	public class Wifi {
		private static volatile Wifi instance;
		private static object syncRoot = new Object();
		protected WlanClient client;
		private WlanClient.WlanInterface wlanInterface = null;

		public NetworkInterface Interface {
			get {
				if (null != wlanInterface) {
					return wlanInterface.NetworkInterface;
				} else {
					return null;
				}
			}

			set {
				try {
					wlanInterface = client.Interfaces.First(iface => iface.InterfaceGuid == new Guid(value.Id));
				} catch (ArgumentNullException) {
					Logger.Critical("Wireless Interface not found");
				}
			}
		}

		private String securityKey;
		public String SecurityKey {
			get {
				switch (SecurityType) {
					default:
					case WifiSecurityType.Open:
						return "";
						//break;
					case WifiSecurityType.WEP:
						if (securityKey.Length == 5 || securityKey.Length == 13) {
							return securityKey;
						} else if (securityKey.Length > 13) {
							return securityKey.Substring(0, 13);
						} else if (securityKey.Length > 5) {
							return securityKey.Substring(0, 5);
						} else {
							throw new ArgumentException();
						}
						//break;
					case WifiSecurityType.WPA2_PSK:
					case WifiSecurityType.WPA_PSK:
						if (securityKey.Length > 63) {
							return securityKey.Substring(0, 63);
						} else if (securityKey.Length < 8) {
							throw new ArgumentException();
						} else {
							return securityKey;
						}
						//break;
				}
			}

			set {
				securityKey = value;
			}
		}

		private WifiSecurityType securityType;
		public WifiSecurityType SecurityType {
			get {
				return securityType;
			}

			set {
				securityType = value;

				switch ((WifiSecurityType) value) {
					default:
					case WifiSecurityType.Open:
						if (EncryptionType != WifiEncryptionType.none) {
							EncryptionType = WifiEncryptionType.none;
						}
						break;
					case WifiSecurityType.WEP:
						if (EncryptionType != WifiEncryptionType.WEP) {
							EncryptionType = WifiEncryptionType.WEP;
						}
						break;
					case WifiSecurityType.WPA2_PSK:
					case WifiSecurityType.WPA_PSK:
						if (EncryptionType != WifiEncryptionType.AES && EncryptionType != WifiEncryptionType.TKIP) {
							EncryptionType = WifiEncryptionType.AES;
						}
						break;
				}
			}
		}

		private WifiEncryptionType encryptionType;
		public WifiEncryptionType EncryptionType {
			get {
				return encryptionType;
			}

			set {
				encryptionType = value;

				switch ((WifiEncryptionType) value) {
					default:
					case WifiEncryptionType.none:
						SecurityType = WifiSecurityType.Open;
						break;
					case WifiEncryptionType.WEP:
						SecurityType = WifiSecurityType.WEP;
						break;
					case WifiEncryptionType.AES:
					case WifiEncryptionType.TKIP:
						if (SecurityType != WifiSecurityType.WPA2_PSK && SecurityType != WifiSecurityType.WPA_PSK) {
							SecurityType = WifiSecurityType.WPA_PSK;
						}
						break;
				}
			}
		}

		private Wifi() {
			client = new WlanClient();
			SecurityKey = "";
			SecurityType = WifiSecurityType.Open;
			EncryptionType = WifiEncryptionType.none;
		}

		protected String generateProfileXml() {
			String profile = "<?xml version=\"1.0\"?><WLANProfile xmlns=\"http://www.microsoft.com/networking/WLAN/profile/v1\"><name>{0}</name><SSIDConfig><SSID><name>{0}</name></SSID></SSIDConfig><connectionType>ESS</connectionType><connectionMode>auto</connectionMode><autoSwitch>false</autoSwitch><MSM><security><authEncryption><authentication>";

			switch (SecurityType) {
				default:
				case WifiSecurityType.Open:
					profile += "open";
					break;
				case WifiSecurityType.WEP:
					profile += "open";
					break;
				case WifiSecurityType.WPA_PSK:
					profile += "WPAPSK";
					break;
				case WifiSecurityType.WPA2_PSK:
					profile += "WPA2PSK";
					break;
			}

			profile += "</authentication><encryption>";

			profile += EncryptionType;

			profile += "</encryption><useOneX>false</useOneX></authEncryption>";

			if (SecurityType != WifiSecurityType.Open) {
				profile += "<sharedKey><keyType>";

				switch (SecurityType) {
					default:
					case WifiSecurityType.WEP:
						profile += "networkKey";
						break;
					case WifiSecurityType.WPA2_PSK:
					case WifiSecurityType.WPA_PSK:
						profile += "passPhrase";
						break;
				}

				profile += "</keyType><protected>false</protected><keyMaterial>{1}</keyMaterial></sharedKey>";

				if (SecurityType == WifiSecurityType.Open || SecurityType == WifiSecurityType.WEP) {
					profile += "<keyIndex>0</keyIndex>";
				}
			}

			profile += "</security></MSM></WLANProfile>";

			profile = String.Format(profile, SSID, SecurityKey);

			Logger.Log(profile);

			return profile;
		}

		public void Disconnect() {
			Logger.Debug("Trying to disconnect from associated network...");
			if (null != wlanInterface) {
				wlanInterface.Disconnect();
				Logger.Debug("Disconnected from associated network");
			}
		}

		public Boolean Connect() {
			if (null == Interface) {
				Logger.Critical("Wireless Interface not set");
				return false;
			}

			Logger.Log(String.Format("Setting new profile for SSID '{0}': [Security Key: '{1}', Security Type: '{2}', Encryption Type: '{3}']", SSID, SecurityKey, SecurityType, EncryptionType));

			try {
				wlanInterface.SetProfile(Wlan.WlanProfileFlags.AllUser, generateProfileXml(), true);
			} catch (Exception e) {
				Logger.Debug(e.Message);
				if (e.InnerException != null) {
					Logger.Debug(e.InnerException.Message);
				}
				return false;
			}

			Logger.Debug("Connecting...");
			wlanInterface.Connect(Wlan.WlanConnectionMode.Profile, Wlan.Dot11BssType.Any, SSID);

			Logger.Debug("Connection state: " + wlanInterface.InterfaceState.ToString());
			//wlanInterface.Connect(Wlan.WlanConnectionMode.TemporaryProfile, Wlan.Dot11BssType.Infrastructure, SSID, Wlan.WlanConnectionFlags.)

			return false;
		}

		public String SSID {
			get;
			set;
		}

		public void DeleteProfile() {
			DeleteProfile(SSID);
		}

		public void DeleteProfile(String profileName) {
			if (null != wlanInterface) {
				Wlan.WlanProfileInfo[] profiles = wlanInterface.GetProfiles();
				if (profiles.Count(profile => profile.profileName == profileName) > 0) {
					Logger.Debug("Deleting profile '" + profileName + "'");
					wlanInterface.DeleteProfile(profileName);
				}
			}
		}

		~Wifi() {
			//DeleteProfile(SSID);
		}

		public WifiConnectionState ConnectionState {
			get {
				if (null == wlanInterface) {
					throw new NullReferenceException();
				}
				WifiConnectionState state = new WifiConnectionState();

				state.InterfaceName = wlanInterface.InterfaceName;
				state.BssType = wlanInterface.BssType.ToString();
				if (wlanInterface.InterfaceState == Wlan.WlanInterfaceState.Connected) {
					try {
						state.Channel = wlanInterface.Channel;
					} catch (Exception) {
						state.Channel = 0;
						Logger.Warning("Can\'t get Channel info");
					}
					state.ConnectionProfileName = wlanInterface.CurrentConnection.profileName;
					try {
						state.ConnectionRSSI = wlanInterface.RSSI;
					} catch (Exception) {
						state.ConnectionRSSI = -100;
						Logger.Warning("Can\'t get RSSI info");
					}
					state.ConnectionSSID = Encoding.ASCII.GetString(wlanInterface.CurrentConnection.wlanAssociationAttributes.dot11Ssid.SSID, 0, (int) wlanInterface.CurrentConnection.wlanAssociationAttributes.dot11Ssid.SSIDLength);
				} else {
					state.Channel = 0;
					state.ConnectionProfileName = "Unknown";
					state.ConnectionSSID = "Unknown";
					state.ConnectionRSSI = -100;
				}
				state.InterfaceState = wlanInterface.InterfaceState.ToString();
				state.ConnectionSpeed = wlanInterface.NetworkInterface.Speed;
				state.ConnectionOperationalStatus = wlanInterface.NetworkInterface.OperationalStatus.ToString();
				state.InterfacePhysicalAddress = wlanInterface.NetworkInterface.GetPhysicalAddress().ToString();

				state.ConnectionIPv4UnicastAddress = new byte[4] { 0, 0, 0, 0 };
				state.ConnectionIPv4UnicastMask = new byte[4] { 0, 0, 0, 0 };
				foreach (UnicastIPAddressInformation address in wlanInterface.NetworkInterface.GetIPProperties().UnicastAddresses) {
					if (address.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !address.Address.IsIPv6SiteLocal && !address.Address.IsIPv6Multicast && !address.Address.IsIPv6LinkLocal) {
						state.ConnectionIPv4UnicastAddress = address.Address.GetAddressBytes();
						IPAddress mask = address.IPv4Mask;
						if (null != mask) {
							state.ConnectionIPv4UnicastMask = mask.GetAddressBytes();
						}
						break;
					}
				}
				state.ConnectionIPv4GatewayAddress = new byte[4] { 0, 0, 0, 0 };
				foreach (GatewayIPAddressInformation address in wlanInterface.NetworkInterface.GetIPProperties().GatewayAddresses) {
					if (address.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !address.Address.IsIPv6SiteLocal && !address.Address.IsIPv6Multicast && !address.Address.IsIPv6LinkLocal) {
						state.ConnectionIPv4GatewayAddress = address.Address.GetAddressBytes();
					}
				}

				state.ConnectionIPv4StatisticsBytesReceived = wlanInterface.NetworkInterface.GetIPv4Statistics().BytesReceived;
				state.ConnectionIPv4StatisticsBytesSent = wlanInterface.NetworkInterface.GetIPv4Statistics().BytesSent;
				state.ConnectionIPv4StatisticsUnicastPacketsSent = wlanInterface.NetworkInterface.GetIPv4Statistics().UnicastPacketsSent;
				state.ConnectionIPv4StatisticsUnicastPacketsReceived = wlanInterface.NetworkInterface.GetIPv4Statistics().UnicastPacketsReceived;
				state.ConnectionIPv4StatisticsOutputQueueLength = wlanInterface.NetworkInterface.GetIPv4Statistics().OutputQueueLength;
				state.ConnectionIPv4StatisticsIncomingPacketsDiscarded = wlanInterface.NetworkInterface.GetIPv4Statistics().IncomingPacketsDiscarded;
				state.ConnectionIPv4StatisticsIncomingPacketsWithErrors = wlanInterface.NetworkInterface.GetIPv4Statistics().IncomingPacketsWithErrors;
				state.ConnectionIPv4StatisticsIncomingUnknownProtocolPackets = wlanInterface.NetworkInterface.GetIPv4Statistics().IncomingUnknownProtocolPackets;
				state.ConnectionIPv4StatisticsOutgoingPacketsDiscarded = wlanInterface.NetworkInterface.GetIPv4Statistics().OutgoingPacketsDiscarded;
				state.ConnectionIPv4StatisticsOutgoingPacketsWithErrors = wlanInterface.NetworkInterface.GetIPv4Statistics().OutgoingPacketsWithErrors;

				return state;
			}
		}

		public static Wifi Instance {
			get {
				if (instance == null) {
					lock (syncRoot) {
						if (instance == null)
							instance = new Wifi();
					}
				}
				return instance;
			}
		}
	}
}